package nbcp.myoql.code.generator.db.mongo

import nbcp.base.comm.StringMap
import nbcp.base.comm.const
import nbcp.base.data.Sys
import nbcp.base.db.CodeValue
import nbcp.base.db.KeyValueString
import nbcp.base.db.annotation.*
import nbcp.base.extend.*
import nbcp.base.utils.*
import nbcp.myoql.code.generator.tool.*
import nbcp.myoql.db.comm.VarTable
import java.io.File
import java.lang.reflect.Field
import java.time.LocalDateTime


/**
 * 代码生成器
 */
class MorGenerator4Kotlin {
    private var nameMapping: StringMap = StringMap();

    private fun String.GetSafeKotlinName(): String {
        if (this.isEmpty()) return this;

        return this.split(".").map {
            if (Sys.devKeywords.contains(it)) {
                return@map """`${it}`"""
            }
            return@map it;
        }.joinToString(".")
    }


    fun generateEntity(
        basePackage: String,   //实体的包名
        metaPackageName: String,
        importPackages: Array<String> = arrayOf(),   //import 包名
        entityFilter: ((Class<*>) -> Boolean) = { true },
        nameMapping: StringMap = StringMap(), // 名称转换
        ignoreGroups: List<String> = listOf("MongoBase")  //忽略的包名
    ): List<CodeValue> {
        this.nameMapping = nameMapping;

        var ret = mutableListOf<CodeValue>();
        var p = File.separator;


        var groups = getGroups(basePackage).filter { ignoreGroups.contains(it.key) == false };
        var embClasses = getEmbClasses(groups);

        println("开始生成 mor...")
        var fileHeader = """package ${metaPackageName}

import java.io.*
import nbcp.base.db.*
import nbcp.base.comm.*
import nbcp.base.extend.*
import nbcp.base.enums.*
import nbcp.base.utils.*
import nbcp.myoql.db.*
import nbcp.myoql.db.comm.*
import nbcp.myoql.db.enums.*
import nbcp.myoql.db.mongo.*
import nbcp.myoql.db.mongo.base.*
import nbcp.myoql.db.mongo.component.*
import org.slf4j.LoggerFactory
import org.springframework.stereotype.*
${importPackages.map { "import " + it }.joinToString(const.line_break)}
"""


        embClasses.forEach {
            ret.add(CodeValue(it.simpleName + "Meta.kt", fileHeader + genEmbEntity(it)));
        }

        var count = 0;
        groups.forEach { group ->
            var groupName = group.key
            var groupEntities = group.value.filter(entityFilter);


            var groupFileName = "${StringUtil.getBigCamelCase(groupName)}Group.kt"
            var content = fileHeader +
                    """
@Component("mongo.${groupName}")
@MetaDataGroup(DatabaseEnum.MONGO, "${groupName}")
class ${StringUtil.getBigCamelCase(groupName)}Group : IDataGroup {
    override fun getEntities(): Set<BaseMetaData<out Any>> = setOf(${
                        group.value.map { genVarName(it).GetSafeKotlinName() }.joinToString(", ")
                    })
"""

            println("${groupName}:")



            content += groupEntities.map { (genVarEntity(it).ToTab(1)) }
                .joinToString(const.line_break)


            content += const.line_break

            groupEntities.forEach {
                count++;
                println(
                    "${
                        count.toString().padStart(2, ' ')
                    } 生成实体：${groupName}.${it.simpleName}".ToTab(1)
                )

                content += genEntity(it).ToTab(1)
            }
            content += """
}
"""


            ret.add(CodeValue(groupFileName, content))
        }

        ret.add(
            CodeValue(
                "MoerMetaMap.kt",
                fileHeader +
                        """

fun mongoColumnJoin(vararg args: String): MongoColumnName {
    return MongoColumnName(args.toList().filter { it.HasValue }.joinToString("."))
}


data class MoerMetaMap(val parentPropertyName: String) {
    constructor(vararg args: String): this(args.toList().filter { it.HasValue }.joinToString(".")) {
    }
    
    fun keys(keys: String): String {
        return this.parentPropertyName + "." + keys
    }
}

"""
            )
        );


        ret.add(
            CodeValue(
                "readme.md",

                """
实体生成时间: ${LocalDateTime.now().AsString()}
"""
            )
        );
        println("生成 mor 完成!")

        return ret;
    }

    var maxLevel = 9;


    fun getEntityName(name: String): String {
        var nameValue = name;
        nameMapping.forEach {
            nameValue = nameValue.replace(it.key, it.value)
        }
        return nameValue[0].lowercase() + nameValue.substring(1);
    }

    fun getGroups(basePackage: String): HashMap<String, MutableList<Class<*>>> {
        var ret = HashMap<String, MutableList<Class<*>>>();


        ClassUtil.findClasses(basePackage)
            .filter { it.isAnnotationPresent(DbEntityGroup::class.java) }
            .forEach {

                var groupName = it.getAnnotation(DbEntityGroup::class.java).value;

                if (ret.containsKey(groupName) == false) {
                    ret[groupName] = mutableListOf();
                }

                ret[groupName]!!.add(it)
            }


        return ret
    }

    /**
     * 递归返回嵌入实体。
     */
    private fun findEmbClasses(type: Class<*>, deep: Int = 0): List<Class<*>> {
        if (deep == 6) return listOf();
        var ret = type.AllFields
            .filter {
                if (it.type.IsSimpleType()) return@filter false;
                if (Map::class.java.isAssignableFrom(it.type)) {
                    return@filter false;
                }

                return@filter true;
            }.map {
                if (it.type.isArray) {
                    return@map it.type.componentType;
                }
                if (List::class.java.isAssignableFrom(it.type)) {
                    return@map it.GetFirstGenericType()
                }
                return@map it.type;
            }.filter {
                if (it == null || it.IsSimpleType()) return@filter false;
                if (Map::class.java.isAssignableFrom(it)) {
                    return@filter false;
                }

                return@filter true;
            }
            .distinctBy { it.name }
            .toMutableList()

        var subClasses = mutableListOf<Class<*>>()
        ret.forEach {
            subClasses.addAll(findEmbClasses(it, deep + 1));
        }

        ret.addAll(subClasses);

        return ret.distinctBy { it.name }
    }

    private fun getEmbClasses(groups: Map<String, MutableList<Class<*>>>): List<Class<*>> {
        return groups.values.Unwind()
            .map {
                return@map findEmbClasses(it)
            }
            .Unwind()
            .distinctBy { it.name }
            .sortedBy { it.name }
    }

    private fun getMetaValue(
        fieldName: String,
        fieldType: Class<*>,
        parentTypeName: String
    ): String {

        if (fieldName == "id") {
            return "mongoColumnJoin(this.parentPropertyName, \"_id\")"
        }

        if (fieldType.IsSimpleType()) {
            return "mongoColumnJoin(this.parentPropertyName, \"${fieldName}\")"
        }

        if (fieldType.simpleName == parentTypeName) {
            return "mongoColumnJoin(this.parentPropertyName, \"${fieldName}\") /*:递归类*/"
        }

        if (Map::class.java.isAssignableFrom(fieldType)) {
            return "MoerMetaMap(this.parentPropertyName, \"${fieldName}\") /*:map*/"
        }

        if (fieldType.isArray) {
            return "mongoColumnJoin(this.parentPropertyName, \"${fieldName}\") /*:array*/"
        }

        if (List::class.java.isAssignableFrom(fieldType)) {
            //应该递归.
            return ""
        }

        return """${
            fieldType.name.split(".").last()
        }Meta(mongoColumnJoin(this.parentPropertyName, "${fieldName}"))""";
    }

    private fun getMetaValue(
        field: Field,
        parentType: Class<*>,
        parentTypeName: String,
        deepth: Int
    ): String {

        if (deepth > maxLevel) {
            throw RuntimeException("-------------------已超过最大深度${field.name}:${field.type.name}-----------------");
        }

        val ret = getMetaValue(field.name, field.type, parentTypeName);
        if (ret.HasValue) return ret;


        if (List::class.java.isAssignableFrom(field.type)) {
            val actType = field.GetFirstGenericType()


            val ret2 = getMetaValue(field.name, actType, parentTypeName);
            if (ret2.HasValue) return ret2;
        }

        return "----找不到getMetaValue----"
    }


    /**
     * @return key = 字段， value = 是否是基本类型。
     */
    private fun getEntityValue(name: String, type: Class<*>): Pair<String, Boolean> {

        val retTypeIsBasicType = true;

        if (name == "id") {
            return "\"_id\"" to retTypeIsBasicType;
        }

        if (type.IsSimpleType() ||
            Map::class.java.isAssignableFrom(type)
        ) {
            return "\"${name}\"" to retTypeIsBasicType
        }

        if (List::class.java.isAssignableFrom(type) ||
            type.isArray
        ) {
            //应该递归调用自己.
            return "" to retTypeIsBasicType
        }

        return """${type.name.split(".").last()}Meta("${name}")""" to false;
    }


    private fun getEntityValue1(field: Field, parentType: Class<*>): Pair<String, Boolean> {
        var (ret, retTypeIsBasicType) = getEntityValue(field.name, field.type);
        if (ret.HasValue) return ret to retTypeIsBasicType;


        if (List::class.java.isAssignableFrom(field.type)) {
            var actType = field.GetFirstGenericType()

            var (ret2, retTypeIsBasicType2) = getEntityValue(field.name, actType);
            if (ret2.HasValue) return ret2 to retTypeIsBasicType2;
        } else if (field.type.isArray) {
            var (ret2, retTypeIsBasicType2) = getEntityValue(field.name, field.type.componentType);
            if (ret2.HasValue) return ret2 to retTypeIsBasicType2;
        }

        return "-----找不到getEntityValue-----" to false;
    }

    private fun genEmbEntity(entType: Class<*>): String {
        var entTypeName = entType.name.split(".").last();
        if (entTypeName.endsWith("\$Companion")) {
            return "";
        }

        var props = entType.AllFields
            .filter { it.name != "Companion" }
            .sortedBy {
                if (it.name == "id") return@sortedBy -9
                if (it.name == "name") return@sortedBy -8
                if (it.name == "code") return@sortedBy -7

                return@sortedBy 0
            }
            .map {
                var v1 = getMetaValue(it, entType, entTypeName, 1)

                return@map """
${CnAnnotationUtil.getComment(it)}${
                    KotlinCoderUtil.getAnnotationCodes(it.annotations).map { const.line_break + it }
                        .joinToString("")
                }
val ${it.name} = ${v1}""".removeEmptyLine().ToTab(1)
            }

        var entityTypeName = entTypeName;


        var ent =
            """
${CnAnnotationUtil.getComment(entType)}${
                KotlinCoderUtil.getAnnotationCodes(entType.annotations)
                    .map { const.line_break + it }.joinToString("")
            }
class ${entityTypeName}Meta(private val parentPropertyName: String) : MongoColumnName() {
    constructor(value: MongoColumnName) : this(value.toString()) {}
${props.joinToString(const.line_break)}
    override fun toString(): String {
        return mongoColumnJoin(this.parentPropertyName).toString()
    }
}
"""
        return ent;
    }


    fun genVarName(entType: Class<*>): String {
        var entTypeName = entType.name.split(".").last();
        if (entTypeName.endsWith("\$Companion")) {
            return "";
        }

        return getEntityName(entTypeName);
    }

    private fun genVarEntity(entType: Class<*>): String {
        var entTypeName = entType.name.split(".").last();
        if (entTypeName.endsWith("\$Companion")) {
            return "";
        }

        var entityTypeName = entTypeName + "Entity";
        var entityVarName = getEntityName(entTypeName).GetSafeKotlinName();

        var varTable = entType.getAnnotation(VarTable::class.java)
//        var varDb = entType.getAnnotation(VarDatabase::class.java)
        var tailRemark = "";
        if (varTable != null) {
            tailRemark += " (变表)"
        }
//        if (varDb != null) {
//            tailRemark += " (动态库)"
//        }

        var ret = mutableListOf<String>()
        ret.add("")

        ret.add(
            """
${CnAnnotationUtil.getComment(entType, tailRemark)}
val ${entityVarName} get() = ${entityTypeName}();
"""
        )


        //可能既变表，又变库。可能两个参数。
        var params = KeyValueString();

        if (varTable?.value.HasValue) {
            params.key = StringUtil.getSmallCamelCase(varTable?.value.AsString(""))
            params.value = """"${entityVarName}-${'$'}{${StringUtil.getSmallCamelCase(varTable.value)}}""""
        }

//        if (varDb?.value.HasValue) {
//            params.key = StringUtil.getSmallCamelCase(varDb.value.AsString());
//            params.value = StringUtil.getSmallCamelCase(varDb.value.AsString())
//        }

        if (params.key.HasValue) {
            ret.add("")

            ret.add(
                """
${CnAnnotationUtil.getComment(entType, tailRemark)}
fun ${entityVarName}(${params.key}: String) = ${entityTypeName}(${params.value});"""
            )
        }

        return ret.joinToString(const.line_break);
    }


    /**
     * 核心，生成一个实体
     */
    private fun genEntity(entType: Class<*>): String {

        var entTypeName = entType.name.split(".").last();
        if (entTypeName.endsWith("\$Companion")) {
            return "";
        }

        val props = entType.AllFields
            .filter { it.name != "Companion" }
            .sortedBy {
                if (it.name == "id") return@sortedBy -9
                if (it.name == "name") return@sortedBy -8
                if (it.name == "code") return@sortedBy -7

                return@sortedBy 0
            }
            .map {

                var (retValue, retTypeIsBasicType) = getEntityValue1(it, entType)

                var pv =
                    """
${CnAnnotationUtil.getComment(it)}${
                        KotlinCoderUtil.getAnnotationCodes(it.annotations)
                            .map { const.line_break + it }
                            .joinToString("")
                    } """

                if (retTypeIsBasicType) {
                    return@map """${pv}
val ${it.name} = MongoColumnName(${retValue})""".removeEmptyLine().ToTab(1)
                } else {
                    return@map """${pv}
val ${it.name} = ${retValue}""".removeEmptyLine().ToTab(1)
                }
            }.toSet()

        val entityTypeName = entTypeName + "Entity"
        var dbName = entType.getAnnotation(DbName::class.java)?.value ?: ""

        if (dbName.isEmpty()) {
            dbName = StringUtil.getSmallCamelCase(entType.simpleName)
        }

        val idMethods = mutableSetOf<String>()

        //每一项是 用逗号分隔的主键组合
        val uks = mutableSetOf<String>();



        uks.addAll(FreemarkerUtil.getEntityUniqueIndexesDefine(entType))


        uks.forEach { uk ->
            var keys = uk.split(",")

            //检测
            keys.forEach {
                if (entType.GetFieldPath(*it.split(".").toTypedArray()) == null) {
                    throw RuntimeException("${entTypeName} 找不到 ${it} 属性!")
                }
            }


            idMethods.add(
                """
    fun queryBy${
                    keys.map { StringUtil.getBigCamelCase(it) }.joinToString("")
                }(${
                    keys.map {
                        "${StringUtil.getSmallCamelCase(it)}: ${
                            entType.GetFieldPath(
                                *it.split(".").toTypedArray()
                            )!!.type.kotlinTypeName
                        }"
                    }.joinToString(",")
                }): MongoQueryClip<${entityTypeName}, ${entType.name}> {
        return this.query()${
                    keys.map {
                        ".where { it.${it} mongoEquals ${
                            StringUtil.getSmallCamelCase(
                                it
                            )
                        } }"
                    }.joinToString("")
                }
    }

    fun deleteBy${
                    keys.map { StringUtil.getBigCamelCase(it) }.joinToString("")
                }(${
                    keys.map {
                        "${StringUtil.getSmallCamelCase(it)}: ${
                            entType.GetFieldPath(
                                *it.split(".").toTypedArray()
                            )!!.type.kotlinTypeName
                        }"
                    }.joinToString(",")
                }): MongoDeleteClip<${entityTypeName}> {
        return this.delete()${
                    keys.map {
                        ".where { it.${it} mongoEquals ${
                            StringUtil.getSmallCamelCase(
                                it
                            )
                        } }"
                    }.joinToString("")
                }
    }

    fun updateBy${
                    keys.map { StringUtil.getBigCamelCase(it) }.joinToString("")
                }(${
                    keys.map {
                        "${StringUtil.getSmallCamelCase(it)}: ${
                            entType.GetFieldPath(
                                *it.split(".").toTypedArray()
                            )!!.type.kotlinTypeName
                        }"
                    }.joinToString(",")
                }): MongoUpdateClip<${entityTypeName}, ${entType.name}> {
        return this.update()${
                    keys.map {
                        ".where { it.${it} mongoEquals ${
                            StringUtil.getSmallCamelCase(
                                it
                            )
                        } }"
                    }.joinToString("")
                }
    }
"""
            )
        }

        var varTable = entType.getAnnotation(VarTable::class.java);

        var varTableRemark = "";
        if (varTable != null) {
            varTableRemark = " (变表)"
//            varTableCode = """${const.line_break}@VarTable("${varTable.value}")"""
        }
//        var varDb = entType.getAnnotation(VarDatabase::class.java)
//        if (varDb != null) {
//            varTableRemark += " (动态库)"
////            varTableCode = """${const.line_break}@VarDatabase("${varDb.value}")"""
//        }


//        var sortNumberAnnotation = entType.getAnnotation(SortNumber::class.java)
//        if (sortNumberAnnotation != null) {
//            sortNumber =
//                """${const.line_break}@SortNumber("${sortNumberAnnotation.field}","${sortNumberAnnotation.groupBy}",${sortNumberAnnotation.step})"""
//        }
//        var logicalDelete = "";
//        var LogicalDeleteAnnotation = entType.getAnnotation(LogicalDelete::class.java)
//        if (LogicalDeleteAnnotation != null) {
//            logicalDelete = """${const.line_break}@LogicalDelete("${LogicalDeleteAnnotation.value}")"""
//        }


        val ent =
            """
${
                CnAnnotationUtil.getComment(
                    entType,
                    varTableRemark
                )
            }${
                KotlinCoderUtil.getAnnotationCodes(entType.annotations)
                    .map { const.line_break + it }.joinToString("")
            }
class ${entityTypeName}(collectionName: String = "${dbName}") :
    MongoBaseMetaCollection<${entType.name.GetSafeKotlinName()}>(${entType.name.GetSafeKotlinName()}::class.java, collectionName) {
${props.map { const.line_break + it }.joinToString(const.line_break)}
${idMethods.joinToString(const.line_break)}
}
"""

        return ent;
    }
}